#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <../arch/arm/mach-s3c2410/include/mach/regs-gpio.h>
#include <../arch/arm/mach-s3c2410/include/mach/hardware.h>


#define DS18B20_PIN   S3C2410_GPB5   //led1
#define DS18B20_PIN_OUTP S3C2410_GPB5_OUTP
#define DS18B20_PIN_INP   S3C2410_GPB5_INP
#define HIGH 1
#define LOW 0

#define MY_MAJOR	250
#define MY_MINOR	0
#define MY_COUNT	1
#define MY_NAME	"HW_DS18B20"



static int _18b20_major,_18b20_minor;
static dev_t _18b20_dev;
static struct cdev *_18b20_cdev;
static int _18b20_open (struct inode *i, struct file *f);
static ssize_t _18b20_read(struct file *filp,char __user *buff,size_t count,loff_t *offp);
typedef unsigned char BYTE;
static BYTE data[2];
static struct class *ds18b20_class;

// DS18B20复位函数
static BYTE DS18b20_reset (void)
{
    // 配置GPIOB0输出模式
    s3c2410_gpio_cfgpin(DS18B20_PIN, DS18B20_PIN_OUTP);
   
    // 向18B20发送一个上升沿，并保持高电平状态约100微秒
    s3c2410_gpio_setpin(DS18B20_PIN, HIGH);
    udelay(100);
   
    // 向18B20发送一个下降沿，并保持低电平状态约600微秒
    s3c2410_gpio_setpin(DS18B20_PIN, LOW);
    udelay(600);
   
    // 向18B20发送一个上升沿，此时可释放DS18B20总线
    s3c2410_gpio_setpin(DS18B20_PIN, HIGH);
    udelay(100);
    
    // 以上动作是给DS18B20一个复位脉冲
    // 通过再次配置GPIOB1引脚成输入状态，可以检测到DS18B20是否复位成功
    s3c2410_gpio_cfgpin(DS18B20_PIN, DS18B20_PIN_INP);
   
    // 若总线在释放后总线状态为高电平，则复位失败
    if(s3c2410_gpio_getpin(DS18B20_PIN)){ printk("DS18b20 reset failed.\r\n"); return 1;}

    return 0;
}

static void DS18b20_write_byte (BYTE byte)
{
    BYTE i;
    // 配置GPIOB1为输出模式
    s3c2410_gpio_cfgpin(DS18B20_PIN, DS18B20_PIN_OUTP);

    // 写“1”时隙：
    //     保持总线在低电平1微秒到15微秒之间
    //     然后再保持总线在高电平15微秒到60微秒之间
    //     理想状态: 1微秒的低电平然后跳变再保持60微秒的高电平
    //
    // 写“0”时隙：
    //     保持总线在低电平15微秒到60微秒之间
    //     然后再保持总线在高电平1微秒到15微秒之间
    //     理想状态: 60微秒的低电平然后跳变再保持1微秒的高电平
    for (i = 0; i < 8; i++)
    {
        s3c2410_gpio_setpin(DS18B20_PIN, LOW); udelay(1);
        if(byte & HIGH)
        {
             // 若byte变量的D0位是1，则需向总线上写“1”
             // 根据写“1”时隙规则，电平在此处翻转为高
             s3c2410_gpio_setpin(DS18B20_PIN, HIGH);
        }
        else
        {
             // 若byte变量的D0位是0，则需向总线上写“0”
             // 根据写“0”时隙规则，电平在保持为低
             // s3c2410_gpio_setpin(DS18B20_PIN, LOW);
        }
        // 电平状态保持60微秒
        udelay(60);

        s3c2410_gpio_setpin(DS18B20_PIN, HIGH);
        udelay(15);

        byte >>= 1;
    }
    s3c2410_gpio_setpin(DS18B20_PIN, HIGH);
}

static BYTE DS18b20_read_byte (void)
{
    BYTE i = 0;
    BYTE byte = 0;
    // 读“1”时隙：
    //     若总线状态保持在低电平状态1微秒到15微秒之间
    //     然后跳变到高电平状态且保持在15微秒到60微秒之间
    //      就认为从DS18B20读到一个“1”信号
    //     理想情况: 1微秒的低电平然后跳变再保持60微秒的高电平
    //
    // 读“0”时隙：
    //     若总线状态保持在低电平状态15微秒到30微秒之间
    //     然后跳变到高电平状态且保持在15微秒到60微秒之间
    //     就认为从DS18B20读到一个“0”信号
    //     理想情况: 15微秒的低电平然后跳变再保持46微秒的高电平
    for (i = 0; i < 8; i++)
    {
        s3c2410_gpio_cfgpin(DS18B20_PIN, DS18B20_PIN_OUTP);
        s3c2410_gpio_setpin(DS18B20_PIN, LOW);

        udelay(1);
        byte >>= 1;

        s3c2410_gpio_setpin(DS18B20_PIN, HIGH);
        s3c2410_gpio_cfgpin(DS18B20_PIN, DS18B20_PIN_INP);

        // 若总线在我们设它为低电平之后若1微秒之内变为高
        // 则认为从DS18B20处收到一个“1”信号
        // 因此把byte的D7为置“1”
        if (s3c2410_gpio_getpin(DS18B20_PIN)) byte |= 0x80;
        udelay(60);
    }
    return byte;      
}

static void DS18b20_proc(void)        
{
    while(DS18b20_reset());
   
    udelay(120);
   
    DS18b20_write_byte(0xcc);
    DS18b20_write_byte(0x44);
   
    udelay(5);
   
    while(DS18b20_reset());
    udelay(200);
   
    DS18b20_write_byte(0xcc);
    DS18b20_write_byte(0xbe);
   
    data[0] = DS18b20_read_byte();
    data[1] = DS18b20_read_byte();
} 

static ssize_t _18b20_read(struct file *filp, char *buffer,size_t count, loff_t *ppos)
{
    DS18b20_proc();

    buffer[0] = data[0];
    buffer[1] = data[1];

  return 1;
}

static int _18b20_open(struct inode *node, struct file *file)
{
  unsigned char flag;
 
  flag = DS18b20_reset();
  if(flag&0x01)
    {
      printk("uable to open ds18b20!\n");
      return -1;
    }
  else
   {
      printk("18B20 opened!\n");
      return 0;
   }
}


static struct file_operations _18b20_fops = {
  .owner = THIS_MODULE,
  .read = _18b20_read,
  .open = _18b20_open,
};

static int __init _18b20_init(void)
{
	int r;
//assign device's major and minor!!
	if(MY_MAJOR)
	{
		_18b20_dev=MKDEV(MY_MAJOR,MY_MINOR);
		r=register_chrdev_region(_18b20_dev,MY_COUNT,MY_NAME);
	}
	if(r<0)
		r=alloc_chrdev_region(&_18b20_dev, MY_MINOR, MY_COUNT, MY_NAME);
	_18b20_major=MAJOR(_18b20_dev);
	_18b20_minor=MINOR(_18b20_dev);

	if(r<0)
	{
		printk(KERN_WARNING "Can't get major !!\n");
		return r;
	}
	else
		printk("The major is %d, and the minor is %d!\n",_18b20_major,_18b20_minor);
//assign device's major and minor!!

//register char device	
	_18b20_cdev= cdev_alloc();
	if(_18b20_cdev!=NULL)
	{
		cdev_init(_18b20_cdev, &_18b20_fops);
		_18b20_cdev->ops = &_18b20_fops;
		_18b20_cdev->owner=THIS_MODULE;	
	 	r=cdev_add(_18b20_cdev, _18b20_dev, MY_COUNT);	
	 	
 		if(r)
		{
			printk(KERN_NOTICE "Error %d adding scull", r);
			return 1;
		}
		else
		{
			ds18b20_class = class_create(THIS_MODULE, MY_NAME);
			if(IS_ERR(ds18b20_class))
			{
				 printk("Err: failed in ds18b20 class. \n");
				 return 1;
			}
			device_create(ds18b20_class, NULL, MKDEV(_18b20_major, 0), NULL, MY_NAME);
			printk(KERN_NOTICE "Adding Luyx__18b20 is successfully!!\n");
		//	while(DS18b20_reset()); 
			return 0;
		}	
	}
	else
	{
		printk(KERN_NOTICE "Error %d adding ds18b20", r);
		return 1;
	}
//register char device	
}

static void __exit _18b20_exit(void)
{
	cdev_del(_18b20_cdev);
	unregister_chrdev_region(_18b20_dev, MY_COUNT);	
	device_destroy(ds18b20_class, MKDEV(_18b20_major, 0));
	class_destroy(ds18b20_class);

	printk(KERN_ALERT "Goodbye, cruel world\n");
}

module_init(_18b20_init);
module_exit(_18b20_exit);
MODULE_DESCRIPTION("DS18B20 driver for s3c2410");
MODULE_LICENSE("GPL");
